use autometrics::{autometrics, prometheus_exporter};
use autometrics_example_util::{run_prometheus, sleep_random_duration};
use axum::{http::StatusCode, response::IntoResponse, routing::get, Router};
use rand::{random, thread_rng, Rng};
use std::error::Error;
use std::net::Ipv4Addr;
use std::time::Duration;
use tokio::net::TcpListener;

// Starting simple, hover over the function name to see the Autometrics graph links in the Rust Docs!
/// This is a simple endpoint that never errors
#[autometrics]
pub async fn get_index() -> &'static str {
    "Hello, World!"
}

/// This is a function that returns an error ~50% of the time
/// The call counter metric generated by autometrics will have a label
/// `result` = `ok` or `error`, depending on what the function returned
#[autometrics]
pub async fn get_random_error() -> Result<(), ()> {
    let should_error = random::<bool>();

    sleep_random_duration().await;

    if should_error {
        Err(())
    } else {
        Ok(())
    }
}

/// This function doesn't return a Result, but we can determine whether
/// we want to consider it a success or not by passing a function to the `ok_if` parameter.
#[autometrics(ok_if = is_success)]
pub async fn route_that_returns_into_response() -> impl IntoResponse {
    (StatusCode::OK, "Hello, World!")
}

/// Determine whether the response was a success or not
fn is_success<R>(response: &R) -> bool
where
    R: Copy + IntoResponse,
{
    response.into_response().status().is_success()
}

#[tokio::main]
pub async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
    // Run Prometheus and generate random traffic for the app
    // (You would not actually do this in production, but it makes it easier to see the example in action)
    let _prometheus = run_prometheus(false);
    tokio::spawn(generate_random_traffic());

    let app = Router::new()
        .route("/", get(get_index))
        .route("/random-error", get(get_random_error))
        // Expose the metrics for Prometheus to scrape
        .route(
            "/metrics",
            get(|| async { prometheus_exporter::encode_http_response() }),
        );

    let listener = TcpListener::bind((Ipv4Addr::from([127, 0, 0, 1]), 3000)).await?;
    let addr = listener.local_addr()?;

    println!(
        "The example API server is now running on: {addr}

Wait a few seconds for the traffic generator to create some fake traffic.
Then, hover over one of the HTTP handler functions (in your editor) to bring up the Rust Docs.

Click on one of the Autometrics links to see the graph for that handler's metrics in Prometheus."
    );

    axum::serve(listener, app).await?;
    Ok(())
}

/// Make some random API calls to generate data that we can see in the graphs
pub async fn generate_random_traffic() {
    let client = reqwest::Client::new();
    loop {
        let request_type = thread_rng().gen_range(0..2);
        let sleep_duration = Duration::from_millis(thread_rng().gen_range(10..50));
        match request_type {
            0 => {
                let _ = client.get("http://localhost:3000").send().await;
            }
            1 => {
                let _ = reqwest::get("http://localhost:3000/random-error").await;
            }
            _ => unreachable!(),
        }
        tokio::time::sleep(sleep_duration).await
    }
}
